package org.test4j.mock.faking.util;

import static org.test4j.mock.faking.util.AsmConstant.Mock_NameSpace;

/**
 * Provides utility methods to extract and filter stack trace information.
 */
public final class StackTrace {
    private final Throwable throwable;

    private StackTraceElement[] elements;

    public StackTrace(Throwable throwable) {
        this.throwable = throwable;
        elements = throwable.getStackTrace();
    }

    public static void filterStackTrace(Throwable t) {
        if (t != null) {
            new StackTrace(t).filter();
        }
    }

    private static boolean isJDKOr3rdPartyLibraryInternalMethod(StackTraceElement ste) {
        String className = ste.getClassName();
        return className.startsWith("sun.") && !ste.isNativeMethod() ||
            className.startsWith("jdk.") ||
            className.startsWith("java.util.") ||
            className.contains(".reflect.") ||
            className.contains(".surefire.") ||
            className.contains(".gradle.") ||
            className.contains(".intellij.") ||
            className.contains(".jdt.");
    }

    private static boolean isTestFrameworkMethod(String where) {
        return where.startsWith("org.junit.") || where.startsWith("org.testng.");
    }

    /**
     * 是否mock框架反复
     *
     * @param where
     * @return
     */
    private static boolean isMockFrameMethod(String where) {
        if (!where.startsWith(Mock_NameSpace)) {
            return false;
        }
        int p = where.indexOf('$');
        if (p < 0) {
            int q = where.lastIndexOf("Test");
            return q < 0 || q + 4 < where.length();
        }

        int q = where.lastIndexOf("Test", p - 4);
        if (q < 0) {
            return true;
        }
        q += 4;
        return q < where.length() && where.charAt(q) != '$';
    }

    public int getDepth() {
        return elements.length;
    }

    public StackTraceElement getElement(int index) {
        return elements[index];
    }

    public void filter() {
        StackTraceElement[] filteredST = new StackTraceElement[elements.length];
        int i = 0;

        for (StackTraceElement ste : elements) {
            if (ste.getFileName() != null) {
                String where = ste.getClassName();
                if (!isJDKOr3rdPartyLibraryInternalMethod(ste) && !isTestFrameworkMethod(where) && !isMockFrameMethod(where)) {
                    filteredST[i] = ste;
                    i++;
                }
            }
        }

        StackTraceElement[] newStackTrace = new StackTraceElement[i];
        System.arraycopy(filteredST, 0, newStackTrace, 0, i);
        throwable.setStackTrace(newStackTrace);
        elements = newStackTrace;

        if (throwable.getCause() != null) {
            new StackTrace(throwable.getCause()).filter();
        }
    }
}